package cc.jq1024.middleware.encrypt.advice;


import cc.jq1024.middleware.encrypt.annotations.Encrypt;
import cc.jq1024.middleware.encrypt.annotations.EncryptBody;
import cc.jq1024.middleware.encrypt.annotations.EncryptIgnore;
import cc.jq1024.middleware.encrypt.config.EncryptProperties;
import cc.jq1024.middleware.encrypt.domain.entity.EncryptAnnotationInfoBean;
import cc.jq1024.middleware.encrypt.domain.entity.Response;
import cc.jq1024.middleware.encrypt.domain.valobj.EncryptKey;
import cc.jq1024.middleware.encrypt.domain.valobj.EncryptMethod;
import cc.jq1024.middleware.encrypt.exception.*;
import cc.jq1024.middleware.encrypt.util.AES;
import cc.jq1024.middleware.encrypt.util.HASH;
import cc.jq1024.middleware.encrypt.util.RSA;
import cc.jq1024.middleware.encrypt.util.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;

/**
 * 响应拦截器
 * @author li--jiaqiang
 */
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<Object> {

    private final ObjectMapper objectMapper;

    public EncryptResponseAdvice(ObjectMapper objectMapper){
        this.objectMapper = objectMapper;
    }

    /**
     * 决定是否对给定的方法参数应用此建议。
     * 检查是否有 Encrypt、EncryptBody 注解，并且没有 EncryptIgnore 注解。
     * @return true: 进行解密， false:不进行解密
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        boolean ignore = returnType.hasMethodAnnotation(EncryptIgnore.class);
        if (ignore) {
            return false;
        }
        Annotation[] annotations = returnType.getDeclaringClass().getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Encrypt)
                return true;
        }
        return returnType.hasMethodAnnotation(Encrypt.class) || returnType.hasMethodAnnotation(EncryptBody.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(null == body) return null;
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String str = null;
        try {
            str = objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        try {
            HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            String aesKey = (String) httpServletRequest.getAttribute(EncryptKey.SECURITY_KEY.getValue());
            // 从类上获取加密注解信息
            EncryptAnnotationInfoBean classAnnotation = getClassAnnotation(returnType.getDeclaringClass(), aesKey);
            if(classAnnotation != null) {
                str = handleEncrypt(str, classAnnotation);
            } else {
                // 从方法上获取加密注解信息
                EncryptAnnotationInfoBean methodAnnotation = getMethodAnnotation(returnType, aesKey);
                if (methodAnnotation != null) {
                    str = handleEncrypt(str, methodAnnotation);
                }
            }
            return Response.<String>builder()
                    .code("200")
                    .info("Success")
                    .data(str)
                    .build();
        } catch (EDException e) {
            throw new RuntimeException(e);
        }
    }

    private EncryptAnnotationInfoBean getMethodAnnotation(MethodParameter methodParameter, String aesKey){
        if (methodParameter.hasMethodAnnotation(EncryptBody.class)){
            EncryptBody encryptBody = methodParameter.getMethodAnnotation(EncryptBody.class);
            if (encryptBody != null){
                String key = StringUtils.getOrDefault(encryptBody.key(), aesKey);
                return new EncryptAnnotationInfoBean(key, encryptBody.method());
            }
        }
        if (methodParameter.hasMethodAnnotation(Encrypt.class)){
            Encrypt encrypt = methodParameter.getMethodAnnotation(Encrypt.class);
            if (encrypt != null) {
                String key = StringUtils.getOrDefault(encrypt.key(), aesKey);
                return new EncryptAnnotationInfoBean(key,encrypt.method());
            }
        }
        return null;
    }

    private EncryptAnnotationInfoBean getClassAnnotation(Class<?> clazz, String aesKey){
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Encrypt) {
                Encrypt encrypt = (Encrypt) annotation;
                String key = StringUtils.getOrDefault(encrypt.key(), aesKey);
                return new EncryptAnnotationInfoBean(key, encrypt.method());
            }
        }
        return null;
    }

    private String handleEncrypt(String string, EncryptAnnotationInfoBean infoBean) throws EncryptException, AlgorithmException {
        EncryptMethod method = infoBean.getEncryptMethod();
        if (method == null) {
            throw new EncryptMethodNotFoundException(new Exception(" 'method' is null"));
        }
        // 单项加密 - 不可解密
        if (method == EncryptMethod.SHA1) {
            return HASH.SHA1(string);
        }
        if (method == EncryptMethod.SHA256) {
            return HASH.SHA256(string);
        }
        if (method == EncryptMethod.SHA384) {
            return HASH.SHA384(string);
        }
        if (method == EncryptMethod.SHA512) {
            return HASH.SHA512(string);
        }
        if (method == EncryptMethod.MD2) {
            return HASH.MD2(string);
        }
        if (method == EncryptMethod.MD5) {
            return HASH.MD5(string);
        }
        // AES,RSA,AesWithRsa
        if (method == EncryptMethod.AES){
        String key = infoBean.getKey();
            if (StringUtils.isBankOrEmpty(key)){
                throw new EncryptKeyNotFoundException(new Exception(" 'key' is empty !"));
            }
            return AES.encrypt(string, key);
        }
        if (method == EncryptMethod.RSA) {
            String key = infoBean.getKey();
            if (StringUtils.isBankOrEmpty(key)) {
                throw new EncryptKeyNotFoundException(new Exception(" 'key' is empty !"));
            }
            return RSA.encryptPrivateKey(string, RSA.getPrivateKey(key));
        }
        if (method == EncryptMethod.AesWithRsa) {
            String aesKey = infoBean.getKey();
            // 直接使用 aesKey 加密数据返回
            return AES.encrypt(string, aesKey);
        }
        throw new EncryptNotSupportedException(new Exception());
    }

}
